﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

using System.Text;
using System.Text.Json.Serialization;

namespace GitHubCopilotProxies.Handlers;

public class AuthGitHub
{
    private sealed class ClientAuthInfo
    {
        [JsonPropertyName("client_id")]
        public required string ClientId { get; set; }
        [JsonPropertyName("device_code")]
        public required string DeviceCode { get; set; }
        [JsonPropertyName("user_code")]
        public required string UserCode { get; set; }
        /// <remarks> 通过验证的一个记录码(同UserCode) </remarks>
        [JsonPropertyName("card_code")]
        public string? CardCode { get; set; }
    }
    /// <remarks> VS Code 发起设备认证请求 </remarks>
    public static async Task PostLoginDeviceCodeAsync(HttpContext httpContext, [FromServices] IMemoryCache memoryCache)
    {
        var clientId = httpContext.Request.Query["client_id"].ToString();
        if (string.IsNullOrEmpty(clientId))
        {
            httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
            await httpContext.ResponseJsonAsync("""{"code":-1,"message":"Client id is required."}""");
            return;
        }

        static string GenCode() => Guid.NewGuid().ToString("N").Substring(0, 6);
        static string GenDevicesCode(int codeLen)
        {
            string newUUID = string.Empty;
            while (newUUID.Length < 64)
                newUUID += Guid.NewGuid().ToString("N");
            return newUUID.Substring(0, codeLen);
        }

        string code = GenCode();
        int rep = 0;
        string cacheKey = $"copilot.proxy.{code}";
        bool repeat = memoryCache.Get(cacheKey) is not null;
        while (repeat)
        {
            if (rep > 5) throw new Exception("gen code error");
            code = GenCode();
            cacheKey = $"copilot.proxy.{code}";
            repeat = memoryCache.Get(cacheKey) is not null;
            rep++;
            httpContext.RequestAborted.ThrowIfCancellationRequested();
        }

        string devId = GenDevicesCode(40);
        var authInfo = new ClientAuthInfo
        {
            ClientId = clientId,
            DeviceCode = devId,
            UserCode = code
        };
        memoryCache.Set(cacheKey, authInfo, TimeSpan.FromSeconds(1800));
        cacheKey = $"copilot.proxy.map.{devId}";
        memoryCache.Set(cacheKey, code, TimeSpan.FromSeconds(1800));

        await httpContext.ResponseJsonAsync($$"""
        {
            "device_code": "{{devId}}",
            "user_code": "{{code}}",
            "verification_uri": "https://copilot.supercopilot.top/login/device?user_code={{code}}",
            "expires_in": 1800,
            "interval": 5
        }
        """);
    }
    /// <summary> VS Code 弹窗页面授权登陆 </summary>
    /// <remarks> 直接授权通过并返回登陆结果 </remarks>
    public static async Task UserLoginDeviceAsync(HttpContext httpContext, [FromServices] IMemoryCache memoryCache)
    {
        if (!httpContext.Request.Query.TryGetValue("user_code", out var userCode))
        {
            httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
            await httpContext.Response.WriteAsync("未提供授权码", httpContext.RequestAborted);
            return;
        }

        var authCode = userCode.ToString();

        string cacheKey = $"copilot.proxy.{authCode}";
        var authInfo = memoryCache.Get<ClientAuthInfo>(cacheKey);
        if (authInfo == null)
        {
            httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
            await httpContext.Response.WriteAsync("授权码无效", httpContext.RequestAborted);
            return;
        }

        var uCode = memoryCache.Get<string>($"copilot.proxy.map.{authInfo.DeviceCode}");
        if (string.IsNullOrEmpty(uCode))
        {
            httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
            await httpContext.Response.WriteAsync("设备识别码无效", httpContext.RequestAborted);
            return;
        }

        authInfo.CardCode = authCode;
        memoryCache.Set(cacheKey, authInfo, TimeSpan.FromSeconds(1800));
        httpContext.Response.ContentType = "text/html; charset=utf-8";
        await httpContext.Response.WriteAsync("<h1>OK!</h1><script>window.onload=()=>{alert('请返回 VS Code 查看登陆结果');window.close()}</script>", Encoding.UTF8, httpContext.RequestAborted);
    }

    /// <summary> VS 2022 弹窗页面授权登陆 </summary>
    /// <remarks> 直接授权通过并跳转登陆返回 </remarks>
    public static async Task GetLoginOauthAuthorizeAsync(HttpContext httpContext, [FromServices] IMemoryCache memoryCache)
    {
        if (httpContext.Request.Query.TryGetValue("client_id", out var clientId) == false ||
            httpContext.Request.Query.TryGetValue("redirect_uri", out var redirectUri) == false ||
            httpContext.Request.Query.TryGetValue("state", out var state) == false)
        {
            await httpContext.ResponseJsonAsync("""{"code":-1,"message":"Invalid request."}""");
            return;
        }

        const string vsCopilotClientId = "a200baed193bb2088a6e";
        if (clientId != vsCopilotClientId)
        {
            await httpContext.ResponseJsonAsync("""{"code":-1,"message":"Invalid client id."}""");
            return;
        }

        static string GenerateOAuthCode(int length)
        {
            var random = new Random();
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
        }

        var oauthCode = GenerateOAuthCode(20);
        var cai = new ClientOAuthInfo { ClientId = clientId!, Code = oauthCode };

        var cacheKey = $"oauth2_authorize_{clientId}";
        memoryCache.Set(cacheKey, cai, TimeSpan.FromMinutes(5));

        var browserSessionId = GenerateOAuthCode(64);
        var redirectUrl = $"{redirectUri}?browserSessionId={browserSessionId}&code={oauthCode}&state={state}";
        httpContext.Response.Redirect(redirectUrl);
    }

    private class ClientOAuthInfo
    {
        [JsonPropertyName("client_id")]
        public required string ClientId { get; set; }
        [JsonPropertyName("code")]
        public required string Code { get; set; }
        [JsonPropertyName("client_secret")]
        public string? ClientSecret { get; set; }
        [JsonPropertyName("scope")]
        public string? Scope { get; set; }
    }
    /// <summary> 登陆成功后获取用户Token </summary>
    /// <remarks> 由于设计为仅本地使用，返回一个伪Token，实际调用不认证 </remarks>
    public static async Task GetLoginOauthAccessTokenAsync(HttpContext httpContext, [FromServices] IMemoryCache memoryCache)
    {
        ClientAuthInfo? GetClientAuthInfo(string code)
        {
            return memoryCache.Get<ClientAuthInfo>($"copilot.proxy.{code}");
        }
        ClientAuthInfo? GetClientAuthInfoByDeviceCode(string deviceCode)
        {
            var cacheKey = $"copilot.proxy.map.{deviceCode}";
            var userCode = memoryCache.Get<string>(cacheKey);
            if (string.IsNullOrEmpty(userCode))
                return null;
            return GetClientAuthInfo(userCode);
        }

        // VS2022 Oauth登录
        if (httpContext.Request.Headers.UserAgent.FirstOrDefault()?.Contains("VSTeamExplorer") is true)
        {
            httpContext.Request.Form.TryGetValue("client_id", out var clientId);
            httpContext.Request.Form.TryGetValue("code", out var code);
            httpContext.Request.Form.TryGetValue("client_secret", out var clientSecret);
            httpContext.Request.Form.TryGetValue("scope", out var scope);
            var checkInfoClient = new ClientOAuthInfo() { ClientId = clientId!, Code = code!, ClientSecret = clientSecret, Scope = scope };
            var cacheKey = $"oauth2_authorize_{clientId}";
            var cliAuthInfo = memoryCache.Get<ClientOAuthInfo>(cacheKey);
            if (cliAuthInfo is null)
            {
                await httpContext.ResponseJsonAsync("""{"code":-1,"message":"Invalid client id."}""");
                return;
            }

            await httpContext.ResponseJsonAsync($$"""
            {
                "access_token": "gho_{{Guid.NewGuid():N}}",
                "scope":        "{{cliAuthInfo.Scope}}",
                "token_type":   "bearer"
            }
            """);
            memoryCache.Remove(cacheKey);
            return;
        }
        else
        {
            if (httpContext.Request.Query.TryGetValue("client_id", out var clientId) == false ||
                httpContext.Request.Query.TryGetValue("device_code", out var deviceCode) == false ||
                httpContext.Request.Query.TryGetValue("grant_type", out var GrantType) == false)
            {
                await httpContext.ResponseJsonAsync("""{"code":-1,"message":"Invalid client id."}""");
                return;
            }

            var authInfo = GetClientAuthInfoByDeviceCode(deviceCode!);
            if (authInfo is not { CardCode.Length: > 0 })
            {
                await httpContext.ResponseJsonAsync("""
                {
                    "error":             "authorization_pending",
                    "error_description": "The authorization request is still pending.",
                    "error_uri":         "https://docs.github.com/developers/apps/authorizing-oauth-apps#error-codes-for-the-device-flow"
                }
                """);
                return;
            }
            if (authInfo is null)
            {
                await httpContext.ResponseJsonAsync("""
                {
                    "error":             "access_denied",
                    "error_description": "You must make a new request for a device code.",
                    "error_uri":         "https://docs.github.com/developers/apps/authorizing-oauth-apps#error-codes-for-the-device-flow"
                }
                """);
                return;
            }

            memoryCache.Remove($"copilot.proxy.map.{deviceCode}");
            memoryCache.Remove($"copilot.proxy.{authInfo.UserCode}");
            await httpContext.ResponseJsonAsync($$"""
            {
                "access_token": "gho_{{Guid.NewGuid():N}}",
                "scope":        "",
                "token_type":   "bearer"
            }
            """);
        }
    }

    /// <summary> VS 2022 有效性验证返回标头 </summary>
    public static async Task GetSiteShaAsync(HttpContext httpContext)
    {
        httpContext.Response.Headers["X-GitHub-Request-Id"] = "C0E1:6A1A:1A1F:2A1D:1A1F:1A1F:1A1F:1A1F";
        await httpContext.ResponseJsonAsync("{}");
    }
}
